package gitee.derumin.really_forge.block_entities;

import gitee.derumin.really_forge.Really_forge;
import gitee.derumin.really_forge.helpers.ForgingResultHelper;
import gitee.derumin.really_forge.items.AvailableInForging;
import net.minecraft.block.AnvilBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtFloat;
import net.minecraft.nbt.NbtList;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.stat.Stats;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;

import java.util.Iterator;

public class AnvilBlockEntity extends BlockEntity {

    private final DefaultedList<ItemStack> inventory;
    private final DefaultedList<Offsets> offsets;
    private final ForgingResultHelper resultHelper;
    public AnvilBlockEntity(BlockPos pos, BlockState state) {
        super(Really_forge.ANVIL_BLOCK_ENTITY, pos, state);
        this.offsets = DefaultedList.ofSize(2, new Offsets(0, 0, 0));
        this.inventory = DefaultedList.ofSize(2, ItemStack.EMPTY);
        this.resultHelper = new ForgingResultHelper(this.world, this.getPos());
    }

    @Override
    public void writeNbt(NbtCompound nbt){
        Inventories.writeNbt(nbt, this.inventory, true);
        NbtList offsets = new NbtList();
        offsets.add(this.offsets.get(0).toNbtList());
        offsets.add(this.offsets.get(1).toNbtList());
        nbt.put("Offsets",offsets);
        this.resultHelper.writeNbt(nbt);
    }

    @Override
    public void readNbt(NbtCompound nbt){
        this.inventory.clear();
        Inventories.readNbt(nbt, this.inventory);
        NbtList offsets = nbt.getList("Offsets",9);
        this.offsets.set(0, new Offsets(offsets.getList(0)));
        this.offsets.set(1, new Offsets(offsets.getList(1)));
        this.resultHelper.readNbt(nbt);
    }
    @Nullable
    @Override
    public Packet<ClientPlayPacketListener> toUpdatePacket() {
        return BlockEntityUpdateS2CPacket.create(this);
    }

    @Override
    public NbtCompound toInitialChunkDataNbt() {
        return this.createNbt();
    }

    @Override
    public void setWorld(World world) {
        this.world = world;
        this.resultHelper.setWorld(world);
    }

    public ItemStack getItemStack(int i){
        if (i > -1 && i < 2){
            return this.inventory.get(i);
        }
        return ItemStack.EMPTY;
    }

    public float getXOffset(int index){
        return this.offsets.get(index).xOffset;
    }

    public float getYaw(int index){
        return this.offsets.get(index).yYaw;
    }

    public float getZOffset(int index){
        return this.offsets.get(index).zOffset;
    }

    public float getProgress(){
        return this.resultHelper.getProgress();
    }

    public ActionResult interactWith(PlayerEntity player, Hand hand, World world){
        if (player.getItemCooldownManager().isCoolingDown(player.getMainHandStack().getItem())){
            return ActionResult.PASS;
        }
        if (player.getStackInHand(hand).isEmpty()){
            if (!world.isClient){
                player.setStackInHand(hand,this.getItem());
            }
            return ActionResult.SUCCESS;
        }else switch (this.getContainingNumber()){
            case 0:
            case 1:
                if (!world.isClient){
                    this.addItem(player.getStackInHand(hand), player.getYaw());
                }
                return ActionResult.CONSUME;
            case 2:
                if (player.getMainHandStack().getItem() instanceof AvailableInForging){
                    if (!world.isClient){
                        this.tryForge(player);
                    }
                    return ActionResult.SUCCESS;
                }else return ActionResult.PASS;
            default:
                return ActionResult.FAIL;
        }
    }

    public void tryForge(PlayerEntity player){
        assert this.world != null;
        ItemStack stack = player.getMainHandStack();
        if (!this.world.isClient && stack.getItem() instanceof AvailableInForging forgeItem){
            player.getItemCooldownManager().set(stack.getItem(), 20);
            if (!player.isCreative()){
                stack.damage(2,player,(player1 -> {}));
            }
            if (this.resultHelper.tryForge(player, this.inventory, forgeItem.getPower(stack), forgeItem.increasePunishment(player.getRandom().nextFloat(), stack))){
                this.clearInventory();
                player.incrementStat(Stats.INTERACT_WITH_ANVIL);
            }
            BlockState blockState = this.world.getBlockState(this.pos);
            if (!player.isCreative() && blockState.isIn(BlockTags.ANVIL)
                    && player.getRandom().nextFloat() < forgeItem.getAnvilBreakPossibility(stack)) {
                BlockState blockState2 = AnvilBlock.getLandingState(blockState);
                if (blockState2 == null) {
                    world.removeBlock(pos, false);
                    this.world.playSound((Entity) null, this.pos, SoundEvents.BLOCK_ANVIL_PLACE, SoundCategory.BLOCKS, (float) (1.2+Math.random()*0.2), (float) (0.4+Math.random()*0.05));
                } else {
                    world.setBlockState(pos, blockState2, 2);
                    this.world.playSound((Entity) null, this.pos, SoundEvents.BLOCK_ANVIL_PLACE, SoundCategory.BLOCKS, (float) (1.0+Math.random()*0.1), (float) (0.5+Math.random()*0.05));
                }
            }
        }
        this.needSync();
    }

    private void needSync(){
        markDirty();
        assert this.world != null;
        this.world.updateListeners(pos, this.getCachedState(), this.world.getBlockState(this.pos), Block.NOTIFY_LISTENERS);
    }

    private void clearInventory(){
        this.inventory.set(0,ItemStack.EMPTY);
        this.inventory.set(1,ItemStack.EMPTY);
    }

    private ItemStack getItem(){
        if (!this.inventory.get(1).isEmpty()){
            return this.getAndRemoveItem(1);
        }else if (!this.inventory.get(0).isEmpty()){
            return this.getAndRemoveItem(0);
        }else {
            return ItemStack.EMPTY;
        }
    }

    private ItemStack getAndRemoveItem(int index){
        ItemStack stack = this.inventory.get(index).copy();
        this.inventory.set(index, ItemStack.EMPTY);
        this.offsets.set(index,new Offsets(0,0,0));
        assert this.world != null;
        this.world.playSound((Entity) null, this.pos, SoundEvents.ENTITY_ITEM_FRAME_REMOVE_ITEM, SoundCategory.BLOCKS, (float) (1.0+Math.random()*0.1), (float) (0.8+Math.random()*0.05));
        this.resultHelper.remove();
        this.needSync();
        return stack;
    }

    public int getContainingNumber(){
        int i = 0;
        Iterator<ItemStack> iterator =this.inventory.stream().iterator();
        while (iterator.hasNext()){
            ItemStack itemStack = iterator.next();
            if (!itemStack.isEmpty()){
                i++;
            }
        }
        return i;
    }

    private void addItem(ItemStack stack, float yaw){
        if (this.inventory.get(0).isEmpty()){
            this.inventory.set(0, stack.copy());
            stack.setCount(0);
            this.offsets.set(0, new Offsets(yaw));
        }else if (this.inventory.get(1).isEmpty()) {
            ItemStack stack1 = stack.copy();
            stack.setCount(stack1.getCount()-1);
            stack1.setCount(1);
            this.inventory.set(1,stack1);
            this.offsets.set(1, new Offsets(yaw));
        }
        assert this.world != null;
        this.world.playSound((Entity) null, this.pos, SoundEvents.ENTITY_ITEM_FRAME_ADD_ITEM, SoundCategory.BLOCKS, (float) (1.0+Math.random()*0.1), (float) (0.8+Math.random()*0.05));
        this.resultHelper.remove();
        this.needSync();
    }

    public void dropItems(float yOffset){
        assert this.world != null;
        if (!this.world.isClient){
            while (true){
                ItemStack itemStack = this.getItem();
                if (itemStack.isEmpty()) break;
                ItemEntity itemEntity = new ItemEntity(this.world, this.pos.getX()+0.5, this.pos.getY()+yOffset, this.pos.getZ()+0.5, itemStack);
                itemEntity.setToDefaultPickupDelay();
                this.world.spawnEntity(itemEntity);
            }
            this.resultHelper.remove();
        }
    }

    private static class Offsets{

        private final float xOffset;
        private final float yYaw;
        private final float zOffset;

        Offsets(float xOffset, float yYaw, float zOffset){
            this.xOffset = xOffset;
            this.yYaw = yYaw;
            this.zOffset = zOffset;
        }

        Offsets(float yYaw){
            this.xOffset = (float) (Math.random() * 0.1 + 0.45);
            this.yYaw = yYaw + (float) (Math.random() * 10);
            this.zOffset = (float) (Math.random() * 0.1 + 0.45);
        }

        Offsets(NbtList list){
            this.xOffset = list.getFloat(0);
            this.yYaw = list.getFloat(1);
            this.zOffset = list.getFloat(2);
        }

        NbtList toNbtList(){
            NbtList list = new NbtList();
            list.add(NbtFloat.of(this.xOffset));
            list.add(NbtFloat.of(this.yYaw));
            list.add(NbtFloat.of(this.zOffset));
            return list;
        }

    }
}
